<?php

declare(strict_types=1);

namespace app\xbak\controller;

use app\admin\BaseAdminController;
use think\facade\View;
use think\facade\Request;
use think\facade\Db;
use think\facade\Config;
use app\system\model\LogOperate as Log;
use think\helper\Str;

class Index extends BaseAdminController
{
    /**
     * 备份管理页面
     */
    public function index()
    {
        View::assign('title', '备份列表/数据备份');
        $path = runtime_path() . 'temp/bak/';
        if (!is_dir($path)) {
            mkdir($path, 0777, true);
        }
        $files = glob($path . '*');
        View::assign('files', count($files));
        return View::fetch();
    }

    /**
     * 从备份目录中恢复数据
     */
    public function recover()
    {
        $name = input('name');
        if (empty($name)) {
            return $this->E404();
        }
        if (Request::isAjax()) {
            if ($this->envMode == 'demo'){
                return $this->isDemo();
            }
            $ac = input('ac/d', 0);
            $tmp = runtime_path() . 'temp/bak/';
            $pre = Config::get('database.connections.mysql.prefix');
            // 初始化
            if ($ac == 0) {
                $path = runtime_path() . 'temp/zip/';
                $files = glob($path . '*');
                $file = $path . $name;
                if (!in_array($file, $files)) {
                    return $this->E404();
                }
                $zip = new \ZipArchive();
                if ($zip->open($file) === TRUE) {
                    $zip->extractTo($tmp);
                    $zip->close();
                }
                return json(['msg' => '初始化...']);
            }
            // 导入数据结构
            if ($ac == 1) {
                $file = $tmp . 'structure.sql';
                $sql = file_get_contents($file);
                $sql = str_replace('__PREFIX__', $pre, $sql);
                // 启动事务
                Db::startTrans();
                try {
                    // 提交事务
                    $arr = explode(';', $sql);
                    foreach ($arr as $item) {
                        $item = trim($item);
                        if (empty($item)) {
                            continue;
                        }
                        if (Str::contains($item, $pre . 'admin')) {
                            // 不导入管理员表
                            continue;
                        }
                        Db::execute($item);
                    }
                    Db::commit();
                } catch (\Exception $e) {
                    // 回滚事务
                    Db::rollback();
                }
                return json(['msg' => '导入数据结构...']);
            }
            // 导入数据
            if ($ac == 2) {
                $tab = input('tab/d', 0);
                $page = input('page/d', 1);
                $tables = include $tmp . 'config.php';
                if (isset($tables[$tab])) {
                    $table = $tables[$tab];
                    $table = str_replace('__PREFIX__', $pre, $table);
                    $short = str_replace($pre, '', $table);
                    $file = $tmp . $short . '_' . $page . '.php';
                    if (file_exists($file)) {
                        $content = file_get_contents($file);
                        $arr = explode("\n", $content);
                        $data = [];
                        foreach ($arr as $item) {
                            $item = trim($item);
                            if (empty($item)) {
                                continue;
                            }
                            $arr = json_decode($item, true);
                            if ($short == 'admin' && $arr['id'] == UID) {
                                continue;
                            }
                            $data[] = $arr;
                        }
                        if ($data) {
                            Db::name($short)->replace()->insertAll($data);
                        }
                        $count = include $tmp . 'count.php';
                        return json(['msg' => $table . '-' . $page . '/' . $count[$tab], 'next' => 2]);
                    } else {
                        return json(['msg' => $table . '-done', 'next' => 1]);
                    }
                } else {
                    Log::add('还原数据库', [
                        '文件' => $name,
                    ]);
                    $start = input('start/f', 0);
                    $end = microtime(true);
                    $time = intval($end - $start);
                    return json(['msg' => '还原成功', 'next' => 3, 'done' => '还原完成，耗时' . $this->formatTime($time) . '！']);
                }
            }
        }
        View::assign('title', '还原数据库/备份列表/数据备份');
        View::assign('start', microtime(true));
        View::assign('name', $name);
        return View::fetch();
    }

    /**
     * 备份列表
     */
    public function data()
    {
        $path = runtime_path() . 'temp/zip/';
        if (!is_dir($path)) {
            mkdir($path, 0777, true);
        }
        $files = glob($path . '*');
        $new = [];
        foreach ($files as $file) {
            $time = filemtime($file);
            $arr = explode('/', $file);
            $new[$time] = [
                'name' => end($arr),
                'size' => formatBytes(filesize($file)),
                'time' => date('Y-m-d H:i:s', $time),
            ];
        }
        krsort($new);
        $files = array_values($new);
        return json($files);
    }

    /**
     * 删除文件
     * @param $path 目录路径
     */
    private function deleteFiles($path)
    {
        $files = glob($path . '*');
        foreach ($files as $file) {
            if (is_file($file)) {
                unlink($file);
            }
        }
    }

    /**
     * 备份数据库页面
     */
    public function backup()
    {
        View::assign('title', '备份数据库/备份列表/数据备份');
        if (Request::isAjax()) {
            if ($this->envMode == 'demo'){
                return $this->isDemo();
            }
            $ac = input('ac/d', 0);
            $path = runtime_path() . 'temp/bak/';
            $lock = $path . 'config.php';
            $tables = [];
            if (file_exists($lock)) {
                $tables = include $lock;
            }
            $tpp = 500; // 每次导出的数量
            $pre = Config::get('database.connections.mysql.prefix');
            // 1. 初始化
            if ($ac == 0) {
                $this->deleteFiles($path);
                $arr = Db::query("SHOW TABLE STATUS");
                $new = [];
                $brr = [];
                foreach ($arr as $value) {
                    $name = str_replace($pre, '__PREFIX__', $value['Name']);
                    $short = str_replace('__PREFIX__', '', $name);
                    $count = Db::name($short)->count();
                    $new[] = $name;
                    $brr[] = $count ? ceil($count / $tpp) : 1;
                }
                file_put_contents($lock, '<?php return ' . var_export($new, true) . ';');
                $count_file = $path . 'count.php';
                file_put_contents($count_file, '<?php return ' . var_export($brr, true) . ';');
                return json(['msg' => '初始化 ...']);
            }
            // 2. 导出数据结构
            if ($ac == 1) {
                $sql = '';
                $file = $path . 'structure.sql';
                foreach ($tables as $value) {
                    $name = str_replace('__PREFIX__', $pre, $value);
                    $sql .= "DROP TABLE IF EXISTS `{$value}`;\n";
                    $arr = Db::query("SHOW CREATE TABLE `{$name}`");
                    $create = $arr[0]['Create Table'] . ";\n";
                    $sql .= str_replace($pre, '__PREFIX__', $create);
                }
                file_put_contents($file, $sql);
                return json(['msg' => '导出数据结构...']);
            }
            // 3. 导出数据
            if ($ac == 2) {
                $tab = input('tab/d', 0);
                $page = input('page/d', 0);
                if (isset($tables[$tab])) {
                    $table = $tables[$tab];
                    $table = str_replace('__PREFIX__', $pre, $table);
                    $short = str_replace($pre, '', $table);
                    $count = Db::name($short)->count();
                    if ($count == 0) {
                        return json(['msg' => $table . '-done', 'next' => 1]);
                    } else {
                        $start = $page * $tpp;
                        $arr = Db::query("SELECT * FROM `{$table}` LIMIT {$start},{$tpp}");
                        if (empty($arr)) {
                            return json(['msg' => $table . '-done', 'next' => 1]);
                        } else {
                            $info = $table . '-' . ($page + 1) . '/' . ceil($count / $tpp);
                            $file = $path . $short . '_' . ($page + 1) . '.php';
                            $sql = '';
                            foreach ($arr as $value) {
                                $sql .= json_encode($value) . "\n";
                            }
                            file_put_contents($file, $sql);
                            return json(['msg' => $info, 'next' => 2]);
                        }
                    }
                } else {
                    $info = '压缩打包文件';
                    $zip_path = runtime_path() . 'temp/zip/';
                    $zip = new \ZipArchive();
                    $zip_name = 'bak_' . date('YmdHis') . '.zip';
                    $zip_file = $zip_path . $zip_name;
                    if ($zip->open($zip_file, \ZipArchive::CREATE) === true) {
                        $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
                        foreach ($files as $name => $file) {
                            if (!$file->isDir()) {
                                $file_name = $file->getRealPath();
                                $zip->addFile($file_name, $file->getFilename());
                            }
                        }
                        $zip->close();
                    }
                    Log::add('备份数据库', [
                        '文件' => $zip_name,
                    ]);
                    $start = input('start/f', 0);
                    $end = microtime(true);
                    $time = intval($end - $start);
                    return json(['msg' => $info, 'next' => 3, 'done' => '备份完成，耗时' . $this->formatTime($time) . '！']);
                }
            }
        }
        View::assign('start', microtime(true));
        return View::fetch();
    }

    /**
     * 格式化脚本执行时间
     * @param @seconds 脚本执行时间
     */
    private function formatTime($seconds)
    {
        $hours = floor($seconds / 3600);
        $minutes = floor(($seconds % 3600) / 60);
        $seconds = $seconds % 60;
        if ($hours > 0) {
            return sprintf("%d时%d分%d秒", $hours, $minutes, $seconds);
        } elseif ($minutes > 0) {
            return sprintf("%d分%d秒", $minutes, $seconds);
        } else {
            return sprintf("%d秒", $seconds);
        }
    }

    /**
     * 删除因备份产生的缓存文件
     */
    public function clear()
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $path = runtime_path() . 'temp/bak/';
        $this->deleteFiles($path);
        return json(['msg' => '']);
    }

    /**
     * 删除备份数据库文件
     */
    public function remove()
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $name = input('name');
        if (empty($name)) {
            return json(['msg' => '删除失败']);
        }
        $path = runtime_path() . 'temp/zip/';
        $files = glob($path . '*');
        $file = $path . $name;
        if (in_array($file, $files)) {
            unlink($file);
            Log::add('删除备份文件', [
                '文件' => $name,
            ]);
        }
        return json(['msg' => '']);
    }

    /**
     * 下载备份文件
     */
    public function download()
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $name = input('name');
        if (empty($name)) {
            return $this->E404();
        }
        $path = runtime_path() . 'temp/zip/';
        $files = glob($path . '*');
        $file = $path . $name;
        if (in_array($file, $files)) {
            Log::add('下载备份文件', [
                '文件' => $name,
            ]);
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="' . basename($file) . '"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            header('Content-Length: ' . filesize($file));
            ob_clean();
            flush();
            readfile($file);
        } else {
            return $this->E404();
        }
    }
}
